/* 
 * Funkcijos darbui su puslapiavimu
 */
#include "../include/types.h"
#include "../include/const.h"
#include "../include/limits.h"
#include "../include/isr.h"
#include "../include/errors.h"
#include "../include/ordarray.h"
#include "../include/kernelheap.h"
#include "../include/paging.h"
#include "../include/monitor.h"

page_dir_t *kernel_dir; /* Kernel puslapiavimo direktorija */
page_dir_t *cur_dir; /* Dabartinė puslapiavimo direktorija */
u32int *frames; /* BitMap masyvas */
u32int nframes; /* Kiek yra vienetų */
extern u32int placement_address; /* Pasiekiame iš kernelheap.c */
extern heap_t *uheap;
extern heap_t *kheap;
extern heap_t *cheap;
/* BitMap macros */
#define INDEX_FROM_BIT(a) (a/(8*4)) /* Gauname index */
#define OFFSET_FROM_BIT(a) (a%(8*4)) /* Gauname offseta tame index'e */

/* Bitmap funkcijos */

/* Nustatyti bitą kur priklauso (1) */
static void setframe(u32int frame_addr){
  u32int frame = frame_addr/0x1000;
  u32int idx = INDEX_FROM_BIT(frame);
  u32int off = OFFSET_FROM_BIT(frame);
  frames[idx] |= (0x1 << off);
}

/* Atstatyti bitą kur reikia (2) */
static void clearframe(u32int frame_addr){
  u32int frame = frame_addr/0x1000;
  u32int idx = INDEX_FROM_BIT(frame);
  u32int off = OFFSET_FROM_BIT(frame);
  frames[idx] &= ~(0x1 << off);
}

/* Patikrinti ar bitas nustatytas. gražina 1 arba 0 */
static u32int testframe(u32int frame_addr){
  u32int frame = frame_addr/0x1000;
  u32int idx = INDEX_FROM_BIT(frame);
  u32int off = OFFSET_FROM_BIT(frame);
  return(frames[idx] & (0x1 << off));
}

/* Randa artimiausia laisvą vietą */
static u32int firstframe(){
  u32int i, j;
  for(i = 0; i < INDEX_FROM_BIT(nframes); i++){
    if(frames[i] != 0xFFFFFFFF){ /* Patikriname ar yra bent viena laisva vieta 32-juose bituose */
      for (j = 0; j < 32; j++){
        u32int toTest = 0x1 << j;
        if ( !(frames[i]&toTest) ){
            return i*4*8+j; /* Grąžiname kuris frame yra laisvas */
        }
      }
    }
  }
}
/*---------------------------------------*/

/* Funkcija parinkti frame puslapiui */
void alloc_frame(page_t *page, int is_kernel, int is_writeable){
  if (page->frame != 0){
    return; /* Jei jau paskirtas frame, baigti priskirimą */
  }
  else{
    u32int idx = firstframe();
    if(idx == (u32int)-1){
      PANIC("No free frames!");
      /* Nerado vietos... */
    }
    setframe(idx*0x1000);
    page->present = 1;
    page->rw = (is_writeable)?1:0;
    page->user = (is_kernel)?0:1;
    page->frame = idx;
  }
}

/* Funkcija atlaisvinti frame nuo puslapio */
void free_frame(page_t *page){
  u32int frame;
  if(!(frame=page->frame)){
    return; /* Jei ir taip laisvas, bereikalingai neatlaisvinti */
  }
  else{
    clearframe(frame);
    page->frame = 0x0;
  }
}

/* Page fault tvarkyklė */
static void page_fault(reg_t regs){
  /* Kai įvyksta page fault, tai adresas kuris tai sukūrė įrašomas į cr2 registrą */
  u32int faulting_address;
  asm volatile("mov %%cr2, %0" : "=r" (faulting_address)); /* Išsaugome kuo greičiau, kad neprarastume */
  
  /* Išanalizuojame kodel įvyko page fault */
  int present   = !(regs.err_code & 0x1); /* Page not present */
  int rw = regs.err_code & 0x2;           /* Write operation? */
  int us = regs.err_code & 0x4;           /* Processor was in user-mode? */
  int reserved = regs.err_code & 0x8;     /* Overwritten CPU-reserved bits of page entry? */
  int id = regs.err_code & 0x10;          /* Caused by an instruction fetch? */

  /* Atspauzdiname kodėl taip įvyko */
  monitor_write("Page fault! ( ");
  if(present){
    monitor_write("present ");
  }
  if(rw){
    monitor_write("read-only ");
  }
  if(us){
    monitor_write("user-mode ");
  }
  if(reserved){
    monitor_write("reserved ");
  }
  monitor_write(") at ");
  monitor_write_hex(faulting_address);
  monitor_write("\n");
  PANIC("Page fault");
}

boolean init_paging(){
  short err=FALSE;
  u32int mem_end_page = MEMORY_END; /* 32MB operatyvioji atmintis */
  nframes = mem_end_page / 0x1000; /* Apskaičiuojame kiek frame bus po 4kb */
  frames = (u32int *)kmalloc(INDEX_FROM_BIT(nframes));
  memset(frames, 0, INDEX_FROM_BIT(nframes));
  
  /* Puslapių direktorijos sukūrimas */
  kernel_dir = (page_dir_t *)kmalloc_align(sizeof(page_dir_t));
  memset(kernel_dir, 0, sizeof(page_dir_t));
  kernel_dir->tPhysicalAddr = (u32int)kernel_dir->tPhysical;
  cur_dir = kernel_dir;
    // Map some pages in the kernel heap area.
    // Here we call get_page but not alloc_frame. This causes page_table_t's 
    // to be created where necessary. We can't allocate frames yet because they
    // they need to be identity mapped first below, and yet we can't increase
    // placement_address between identity mapping and enabling the heap!
  /* Kernel */
  int i = 0;
  for(i = KHEAP_START; i < KHEAP_START + KHEAP_INITIAL_SIZE; i += 0x1000){
    get_page(i, 1, kernel_dir);
  }
  /* USER */
  i = 0;
  for(i = UHEAP_START; i < UHEAP_START + UHEAP_INITIAL_SIZE; i += 0x1000){
    get_page(i, 1, kernel_dir);
  }
  
  /* Kernel */
  i = 0;
  while(i < placement_address+0x1000){
    /* Kernelio kodas turi but leistinas skaityti, bet neleistinas įrašinėti iš user mode */
    alloc_frame(get_page(i, 1, kernel_dir), 0, 1);
    i += 0x1000;
  }
  
  /* Kernel */
  /* Priskiriame puslapius anksčiau pažymetus */
  for(i = KHEAP_START; i < KHEAP_START+KHEAP_INITIAL_SIZE; i += 0x1000){
    alloc_frame(get_page(i, 1, kernel_dir), 0, 0);
  }
  
  /* USER */
  /* Priskiriame puslapius anksčiau pažymetus */
  for(i = UHEAP_START; i < UHEAP_START+UHEAP_INITIAL_SIZE; i += 0x1000){
    alloc_frame(get_page(i, 1, kernel_dir), 0, 0);
  }
  register_interrupt_handler(14, &page_fault); /* Susitvarkome su page fault */
  
  switch_page_dir(kernel_dir); /* Įjungiame paging */
  /* Inicijuoja kernel'io heap'ą */
  /* Kernel */
  kheap = create_heap(KHEAP_START, KHEAP_START+KHEAP_INITIAL_SIZE, KHEAP_END, 0, 0);
  uheap = create_heap(UHEAP_START, UHEAP_START+UHEAP_INITIAL_SIZE, UHEAP_END, 0, 0);
  
  /* USER */
  cheap = kheap;
  err = TRUE;
  return err;
}

void switch_page_dir(page_dir_t *dir){
  cur_dir = dir;
  asm volatile("mov %0, %%cr3":: "r"(&dir->tPhysical)); /* Nurodme kur rodo puslapių direktorija */
  u32int cr0;
  asm volatile("mov %%cr0, %0": "=r"(cr0)); /* Gauname cr0 registro reikšmes */
  cr0 |= 0x80000000; /* Įjungiame pakeisdami cr0 flagą */
  asm volatile("mov %0, %%cr0":: "r"(cr0)); /* Išsaugome cr0 pakeistą  */
}

page_t *get_page(u32int address, int make, page_dir_t *dir){
  address /= 0x1000; /* Iš adreso gauname indeksą */
  u32int table_idx = address / 1024; 
  /* Surandame page table kuris turi šį adresą */
  if(dir->tables[table_idx]){ /* Patikriname ar nera jau sukurtas toks page table */
    return &dir->tables[table_idx]->pages[address%1024]; /* Jei taip, tai grąžiname tą puslapį */
  }
  else if(make){ /* Jei make=1 tada sukuriame page table ir tą puslapį, jei dar nesukurtas */
    u32int tmp;
    dir->tables[table_idx] = (page_table_t *)kmalloc_align_physical(sizeof(page_table_t), &tmp);
    dir->tPhysical[table_idx] = tmp | 0x7; /* Nustatome Present, read/write ir user bitus */
    return &dir->tables[table_idx]->pages[address%1024]; /* Grąžiname ta puslapį */
  }
  else{
    return 0; /* Kitu atveju grąžiname nieko */
  }
}
